/**
 *  Version:     @(#)aio_event.c    0.2.05 20/10/2006
 *
 *  Authors:     Hailong Xia <hlxxxx@gmail.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty
 *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *  the GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General
 *  Public License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 *  Boston, MA 02111-1307  USA
 */

#ifndef _AIO_CUSTOM_EVENT

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <assert.h>

#include "aio_log.h"
#include "aio_poll.h"
#include "aio_event.h"
#include "aio_util.h"

static int timeout_process(struct aio_evbase *eb);
static void process(struct aio_evbase *eb, int nready);
static int timeout_next(struct aio_evbase *eb, struct timeval *tv);
static void dispatch_timeout(int fd, short events, void *arg);

static int
gettime(struct timeval *tp)
{
#ifdef HAVE_CLOCK_GETTIME
        struct timespec ts;

        if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
                return -1;
        }
        tp->tv_sec = ts.tv_sec;
        tp->tv_usec = ts.tv_nsec / 1000;
#else
        if (gettimeofday(tp, NULL) == -1) {
                return -1;
        }
#endif
        return 0;
}

static int
time_compare(struct aio_event *a, struct aio_event *b)
{
        if (timercmp(&a->timeout, &b->timeout, <)) 
                return -1;
        else if (timercmp(&a->timeout, &b->timeout, >)) 
                return 1;

        if (a < b) 
                return -1;
        else if (a > b) 
                return 1;

        return 0;
}

RB_GENERATE(time_tree, aio_event, time_node, time_compare);

struct aio_evbase*
aio_evbase_create(int size)
{
        int i;
        struct aio_evbase *eb;

        if (size <= 0) {
                log_err("Invalid argument maxfds: %d\n", size);
                return 0;
        }

        eb = (struct aio_evbase *)calloc(1, sizeof(struct aio_evbase));
        assert(eb);

        if (gettime(&eb->timeflag) < 0) {
                log_err("Get system current time failed.\n");
                free(eb);
                return 0;
        }

        eb->p_fds = (struct pollfd *)calloc(1, sizeof(struct pollfd) * size);
        assert(eb->p_fds);

        eb->r_evs = (struct aio_event **)calloc(1, sizeof(struct aio_event*) * size);
        assert(eb->r_evs);

        eb->w_evs = (struct aio_event **)calloc(1, sizeof(struct aio_event*) * size);
        assert(eb->w_evs);

        eb->size = size;
        eb->maxpfdi = -1;

        for (i = 0; i < eb->size; i++) {
                eb->p_fds[i].fd = -1;
                eb->p_fds[i].events = 0;
                eb->p_fds[i].revents = 0;
        }

        RB_INIT(&eb->timetree);
        eb->exit = 0;

        log_info("aio event init succeed.\n");
        return eb;
}

void
aio_evbase_destroy(struct aio_evbase *eb)
{
        if (!eb) return;

#if 0 /* Unnecessary */
        RB_FOREACH(ev, time_tree, &eb->timetree)
                RB_REMOVE(time_tree, &eb->timetree, ev);
        assert(RB_EMPTY(&eb->timetree));
#endif 

        if (eb->p_fds) {
                free(eb->p_fds);
                eb->p_fds = NULL;
        }

        if (eb->r_evs) {
                free(eb->r_evs);
                eb->r_evs = NULL;
        }

        if (eb->w_evs) {
                free(eb->w_evs);
                eb->w_evs = NULL;
        }

        free(eb);
        log_info("aio event destroy succeed.\n");
}

int 
aio_evbase_set(struct aio_event *ev, struct aio_evbase *eb)
{
        ev->eb = eb;
        return 0;
}

void     
aio_event_set(struct aio_event *ev, int fd, 
        short events, void (*cb)(int, short, void *), void *arg)
{       
        assert(ev && cb);
        assert(events > 0);

        if (ev->events != 0) {
                aio_event_del(ev);
        }

        memset(ev, 0, sizeof(struct aio_event));

        ev->fd = fd;
        ev->events = events;
        ev->cb = cb;
        ev->cbarg = arg;
        ev->timeout.tv_sec = INT_MAX;
        ev->timeout.tv_usec = 0;
        ev->pfdi = -1;
}

int
aio_event_add(struct aio_event *ev, struct timeval *timeout)
{
        int i = 0, fd = 0;
        short events = 0;
        short pollevents = 0;
        struct aio_evbase *eb = 0;
        struct timeval now;

        if (!ev) {
                log_err("Invalid argument input.\n");
                return -1;
        }

        if (!ev->eb) {
                log_err("Invalid pointer.\n");
                return -1;
        }

        if (timeout)
                ev->events |= AIO_EV_TIMEOUT;

        fd = ev->fd;
        events = ev->events;
        eb = ev->eb;

        if (events & AIO_EV_TIMEOUT) {
                struct aio_event *tmpev;
                if (gettime(&now) < 0) {
                        log_err("Get curent system time failed.\n");
                        return -1;
                }
                timeradd(&now, timeout, &ev->timeout);
                tmpev = RB_INSERT(time_tree, &eb->timetree, ev);
                assert(tmpev == NULL);
        }

        if (fd < 0) /* The event only for timeout. */
                return 0;

        if (ev->pfdi < 0) {
                for (i = 0; i <= eb->maxpfdi; i++) {
                        if (eb->p_fds[i].fd == fd)
                                break;
                }

                if (i > eb->maxpfdi) {
                        for (i = 0; i < eb->size; i++) {
                                if (eb->p_fds[i].fd == -1)
                                        break;
                        }
                }
        }
        else {
                i = ev->pfdi;
        }

        if (i >= eb->size) {
                log_warn("The event buffer has full.\n");
                return -1;
        }

        if (events & AIO_EV_READ) {
                pollevents |= POLLIN;
                eb->r_evs[i] = ev;
        }

        if (events & AIO_EV_WRITE) {
                pollevents |= POLLOUT;
                eb->w_evs[i] = ev;
        }

        eb->p_fds[i].fd = fd;
        eb->p_fds[i].events |= pollevents;
        eb->p_fds[i].revents = 0;
        ev->pfdi = i;

        eb->maxpfdi = (i > eb->maxpfdi) ? i : eb->maxpfdi;

        return 0;
}

int
aio_event_del(struct aio_event *ev)
{
        int i, fd, flags;
        struct aio_evbase *eb = 0;

        if (!ev) {
                log_err("Invalid argument input.\n");
                return -1;
        }

        if (!ev->eb) {
                log_err("Invalid pointer.\n");
                return -1;
        }

        fd = ev->fd;
        eb = ev->eb;

        /* Only was called when timer event is canceled */
        if (ev->events & AIO_EV_TIMEOUT) {
                RB_REMOVE(time_tree, &eb->timetree, ev);
                ev->events &= ~AIO_EV_TIMEOUT;
        }

        if (fd < 0 || ev->pfdi < 0 || ev->pfdi > eb->size)
                return 0;

        if (ev->events & AIO_EV_READ) {
                eb->p_fds[ev->pfdi].events &= ~POLLIN;
                eb->r_evs[ev->pfdi] = 0;
        }

        if (ev->events & AIO_EV_WRITE) {
                eb->p_fds[ev->pfdi].events &= ~POLLOUT;
                eb->w_evs[ev->pfdi] = 0;
        }

        if (!(eb->p_fds[ev->pfdi].events & (POLLIN|POLLOUT))) {
                eb->p_fds[ev->pfdi].fd = -1;
                eb->p_fds[ev->pfdi].events = 0;
                eb->p_fds[ev->pfdi].revents = 0;

                if (ev->pfdi < eb->maxpfdi) {
                        ev->pfdi = -1;
                        return 0;
                }

                /* calculate max pfdi again. */
                flags = 0;
                for (i = eb->maxpfdi; i >= 0; i--) {
                        if (eb->p_fds[i].fd != -1) {
                                eb->maxpfdi = i;;
                                flags = 1;
                                break;
                        }
                }

                if (flags == 0)
                        eb->maxpfdi = -1;

                ev->pfdi = -1;
        }

        return 0;
}

int
aio_event_dispatch(struct aio_evbase *eb)
{
        struct timeval tv;
        struct aio_event *ev = 0;
        struct timeval off = {0, 0};
        int nready = 0, millisec = 0;

        if (!eb) {
                log_err("Invalid argument input.\n");
                return -1;
        }

        eb->exit = 0;

        while(eb && !eb->exit) {
                if (gettime(&tv) < 0) {
                        log_err("Get current timer failed.\n");
                        return -1;
                }

                if (timercmp(&tv, &eb->timeflag, <)) {
                        timersub(&eb->timeflag, &tv, &off);
                        RB_FOREACH(ev, time_tree, &eb->timetree) {
                                timersub(&ev->timeout, &off, &ev->timeout);
                        }
                }

                eb->timeflag = tv;
                if (timeout_next(eb, &tv) != 0) {
                        log_err("Call timeout_next failed.\n");
                        return -1;
                }

                millisec = tv.tv_sec * 1000 + (tv.tv_usec + 999) / 1000;
                nready = poll(eb->p_fds, eb->maxpfdi + 1, millisec);
                if (nready < 0) {
                        log_err("Poll failed. err: %d\n", errno);
                        return -1;
                }

                if (timeout_process(eb) != 0) {
                        log_err("Call timeout_process failed.\n");
                        return -1;
                }

                if (nready > 0)
                        process(eb, nready);
        }

        /* eventbase was free in callback function. */
        if (!eb || eb->exit == -1)
                return -1;

        /* timeout case */
        return 0;
}

int
aio_event_shutdown(struct aio_evbase *eb, struct timeval *tv)
{
        assert(eb); 

        if (tv == NULL) return 0;

        aio_evtimer_set(&eb->timer_event, dispatch_timeout, eb);
        aio_evbase_set(&eb->timer_event, eb);

        if (aio_evtimer_add(&eb->timer_event, tv) != 0) {
                log_warn("Add event for dispatch timer failed.\n");
                return -1;
        }
        return 0;
}

static int
timeout_process(struct aio_evbase *eb)
{
        struct timeval now;
        struct aio_event *ev, *next;
        short revents = 0;

        if (!eb) return -1;

        if (gettime(&now) < 0)
                return -1;

        for (ev = RB_MIN(time_tree, &eb->timetree); ev; ev = next) {
                if (timercmp(&ev->timeout, &now, >)) {
                        break;
                }
                next = RB_NEXT(time_tree, &eb->timetree, ev);
                RB_REMOVE(time_tree, &eb->timetree, ev);
                ev->events &= ~AIO_EV_TIMEOUT;
                revents |= AIO_EV_TIMEOUT; 
                (ev->cb)(ev->fd, revents, ev->cbarg);
        }
        return 0;
}

static void
process(struct aio_evbase *eb, int nready)
{
        int i, what = 0;
        struct aio_event *ev;
        short revents;

        if (!eb) return;

        for (i = 0; i <= eb->maxpfdi; i++) {
               if (eb->p_fds[i].fd < 0)
                       continue;

               if (!(what = eb->p_fds[i].revents))
                       continue;

               revents = 0;

               if (what & (POLLHUP|POLLERR|POLLNVAL)) {
                       revents |= AIO_EV_ERROR;
                       if (eb->r_evs[i]) { 
                               ev = eb->r_evs[i];
                               ev->cb(ev->fd, revents, ev->cbarg);
                       } else if (eb->w_evs[i]) {
                               ev = eb->w_evs[i];
                               ev->cb(ev->fd, revents, ev->cbarg);
                       } else {
                               log_err("Aio event suffer a fatal logic error.\n");
                       }
                       goto breakcheck;
               }

               if (what & POLLIN) {
                       if (!(ev = eb->r_evs[i])) 
                               return;

                       if (ev->events & AIO_EV_READ) {
                               revents |= AIO_EV_READ; 

                               if (!(ev->events & (AIO_EV_RPERSIST|AIO_EV_WPERSIST))) {
                                       aio_event_del(ev);
                               } else if (ev->events & AIO_EV_RPERSIST) {
                                       revents |= AIO_EV_RPERSIST; 
                               } else if (ev->events & AIO_EV_WPERSIST) {
                                       revents |= AIO_EV_WPERSIST;
                               }

                               ev->cb(ev->fd, revents, ev->cbarg);
                       }
               }

               if (what & POLLOUT) {
                       if (!(ev = eb->w_evs[i])) 
                               return;

                       if (ev->events & AIO_EV_WRITE) {
                               revents |= AIO_EV_WRITE;

                               if (!(ev->events & (AIO_EV_RPERSIST|AIO_EV_WPERSIST))) {
                                       aio_event_del(ev);
                               } else if (ev->events & AIO_EV_RPERSIST) {
                                       revents |= AIO_EV_RPERSIST;
                               } else if (ev->events & AIO_EV_WPERSIST) {
                                       revents |= AIO_EV_WPERSIST;
                               }

                               ev->cb(ev->fd, revents, ev->cbarg);
                       }
               }

breakcheck:
               if (--nready <= 0)
                       break;
        }
}

static int
timeout_next(struct aio_evbase *eb, struct timeval *tv)
{
        struct aio_event *ev;
        struct timeval now;
        struct timeval dflt = TIMEOUT_DEFAULT;

        if (!eb) return -1;

        if ((ev = RB_MIN(time_tree, &eb->timetree)) == NULL) {
                *tv = dflt;
                return 0;
        }

        if (gettime(&now) < 0) {
                return -1;
        }

        if (timercmp(&ev->timeout, &now, <=)) {
                timerclear(tv);
                return 0;
        }

        timersub(&ev->timeout, &now, tv);

        assert(tv->tv_sec >= 0);
        assert(tv->tv_usec >= 0);
        return 0;
}

static void
dispatch_timeout(int fd, short events, void *arg)
{
        struct aio_evbase *eb = (struct aio_evbase *)arg;

        assert(eb);
        eb->exit = 1;
}

#endif /* _AIO_CUSTOM_EVENT */

